#include "LinuxPlatform.h"

#ifdef LINUX

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <iostream>

#include "Platform/Window/X11Window.h"

#include <vulkan/vulkan.hpp>
#include <vulkan/vulkan_xlib.h>


#include <X11/Xlib.h>
//use Xrandr to get screen information
#include <X11/extensions/Xrandr.h>

class CX11Things
{
public:    
    Display * _Display=nullptr;
    uint64_t LastTimeLeftMouseButtonPressed=0;
    uint64_t LastTimeRightMouseButtonPressed=0;
    uint64_t LastTimeMiddleMouseButtonPressed=0;
    XIM _XIM=0; 
    XIC _XIC=0;
    TMap<Window, CX11Window*> Windows;

    // //the detected event that not yet let application know 
    TVector<XEvent> PendingEvents;

    void ReadAllX11Event()
    {
        int PendingEventNum=XPending(_Display);
        for(int i=0;i<PendingEventNum;i++)
        {
            XEvent& Event= PendingEvents.Emplace();
            XNextEvent(_Display,&Event);
        }
    }


    Window GetClipboardWindow()
    {
        if(ClipboardWindow!=0)
        {
            return ClipboardWindow;
        }

        //create unmapped clipboard window
        int Screen = DefaultScreen(_Display);
        ClipboardWindow= XCreateSimpleWindow(
            _Display
            , RootWindow(_Display, Screen)
            , -10,-10,1,1,0,0,0 );

        return ClipboardWindow;
    }
protected:    

    //a unmapped window to address Clipboard things
    Window ClipboardWindow=0;  
};


CLinuxPlatform::CLinuxPlatform()
{

    X11Things = new CX11Things();


    setlocale(LC_CTYPE, "");  //required by ime input
    XSetLocaleModifiers("");  //required by ime input

    XInitThreads(); //required by multithreaded X11


    X11Things -> _Display = XOpenDisplay(nullptr);

    if(!X11Things -> _Display)
    {
        std::cout<<"XOpenDisplay failed\n";
        return;
    }

}

CLinuxPlatform::~CLinuxPlatform()
{
    assert(X11Things -> _Display);
    XCloseDisplay(X11Things -> _Display);

    delete (CX11Things*)X11Things;
}

SRect CLinuxPlatform::GetDisplayWorkArea(const SIntPoint &InPosition) const
{
    //find screen that contains the position
    int ScreenNum;
    XRRMonitorInfo *MonitorInfo = XRRGetMonitors(X11Things->_Display, DefaultRootWindow(X11Things->_Display), True, &ScreenNum);

    for(int i=0;i<ScreenNum;i++)
    {
        if(InPosition.X>=MonitorInfo[i].x && InPosition.X<MonitorInfo[i].x+MonitorInfo[i].width
            && InPosition.Y>=MonitorInfo[i].y && InPosition.Y<MonitorInfo[i].y+MonitorInfo[i].height)
        {
            SRect Rect;
            Rect.Min.X=MonitorInfo[i].x;
            Rect.Min.Y=MonitorInfo[i].y;
            Rect.Extent.X=MonitorInfo[i].width;
            Rect.Extent.Y=MonitorInfo[i].height;
            return Rect;
        }
    }

    //can not find the screen that contains the position
    return SRect();

}

TVector<const char *> CLinuxPlatform::GetVulkanInstanceExtensions() const
{
    TVector<const char*> Extensions;
    Extensions.Add(VK_KHR_SURFACE_EXTENSION_NAME);
    Extensions.Add(VK_KHR_XLIB_SURFACE_EXTENSION_NAME);
    return Extensions;
}

int64_t CLinuxPlatform::CreateProcess(const CString &Command, const CString &WorkingDirectory)
{
    
    //create a new process on linux
    pid_t pid = fork();

    if (pid == 0)
    {
        //child process
        //execute the command
        CString CDCommand = U"cd " + WorkingDirectory;
        CString FinalCommand = CDCommand +U" && ./"  + Command;
        auto FinalCommand8 = FinalCommand.ToUtf8();
        execl("/bin/sh", "sh", "-c",(const char*)FinalCommand8.Data(), NULL);
    }
    else if (pid > 0)
    {
        //parent process

        //wait for the child process to finish and get the exit code
        int ProcessStatus;
        waitpid(pid, &ProcessStatus, 0);
        return WEXITSTATUS(ProcessStatus);
    }
    else
    {
        //fork failed
        assert(false&& "fork failed");
        return -1;
    }

}

EKey GetKeyFromX11Key(KeyCode KeyCode)
{
    // how to get key code name ?
    // use xev command in terminal and press the key

    switch (KeyCode)
    {
    case 9:
        return EKey::Escape;
    case 10:
        return EKey::Key1;
    case 11:
        return EKey::Key2;
    case 12:
        return EKey::Key3;
    case 13:
        return EKey::Key4;
    case 14:
        return EKey::Key5;
    case 15:
        return EKey::Key6;
    case 16:
        return EKey::Key7;
    case 17:
        return EKey::Key8;
    case 18:
        return EKey::Key9;
    case 19:
        return EKey::Key0;
    case 20:
        return EKey::Minus;
    case 21:
        return EKey::Equal;
    case 22:
        return EKey::Backspace;
    case 23:
        return EKey::Tab;
    case 24:
        return EKey::Q;
    case 25:
        return EKey::W;
    case 26:
        return EKey::E;
    case 27:
        return EKey::R;
    case 28:
        return EKey::T;
    case 29:
        return EKey::Y;
    case 30:
        return EKey::U;
    case 31:
        return EKey::I;
    case 32:
        return EKey::O;
    case 33:
        return EKey::P;
    case 34:
        return EKey::LBracket;
    case 35:
        return EKey::RBracket;
    case 36:
        return EKey::Enter;
    case 37:
        return EKey::LControl;
    case 38:
        return EKey::A;
    case 39:
        return EKey::S;
    case 40:
        return EKey::D;
    case 41:
        return EKey::F;
    case 42:
        return EKey::G;
    case 43:
        return EKey::H;
    case 44:
        return EKey::J;
    case 45:
        return EKey::K;
    case 46:
        return EKey::L;
    case 47:
        return EKey::Semicolon;
    case 48:
        return EKey::Apostrophe;
    case 49:
        return EKey::Tilde;
    case 50:
        return EKey::LShift;
    case 51:
        return EKey::BackSlash;
    case 52:
        return EKey::Z;
    case 53:
        return EKey::X;
    case 54:
        return EKey::C;
    case 55:
        return EKey::V;
    case 56:
        return EKey::B;
    case 57:
        return EKey::N;
    case 58:
        return EKey::M;
    case 59:
        return EKey::Comma;
    case 60:
        return EKey::Period;
    case 61:
        return EKey::Slash;
    case 62:
        return EKey::RShift;
    case 63:
        return EKey::NumPadMultiply;
    case 64:
        return EKey::LAlt;
    case 65:
        return EKey::Space;
    case 66:
        return EKey::CapsLock;
    case 67:
        return EKey::F1;
    case 68:
        return EKey::F2;
    case 69:
        return EKey::F3;
    case 70:
        return EKey::F4;
    case 71:
        return EKey::F5;
    case 72:
        return EKey::F6;
    case 73:
        return EKey::F7;
    case 74:
        return EKey::F8;
    case 75:
        return EKey::F9;
    case 76:
        return EKey::F10;
    case 77:
        return EKey::NumLock;
    case 78:
        return EKey::ScrollLock;
    case 79:
        return EKey::NumPad7;
    case 80:
        return EKey::NumPad8;
    case 81:
        return EKey::NumPad9;
    case 82:
        return EKey::NumPadSubtract;
    case 83:
        return EKey::NumPad4;
    case 84:
        return EKey::NumPad5;
    case 85:
        return EKey::NumPad6;
    case 86:
        return EKey::NumPadAdd;
    case 87:
        return EKey::NumPad1;
    case 88:
        return EKey::NumPad2;
    case 89:
        return EKey::NumPad3;
    case 90:
        return EKey::NumPad0;
    case 91:
        return EKey::NumPadDecimal;
    case 93:
        return EKey::F11;
    case 94:
        return EKey::F12;

    case 104:
        return EKey::NumPadEnter;

    case 106:
        return EKey::NumPadSlash;

    case 108:
        return EKey::RAlt;
    case 111:
        return EKey::Up;

    case 113:
        return EKey::Left;
    case 114:
        return EKey::Right;
    case 115:
        return EKey::RControl;
    case 116:
        return EKey::Down;
    case 133:
        return EKey::LSystem;
    case 134:
        return EKey::RSystem;
    case 135:
        return EKey::Menu;
    
    default:
        return EKey::UnSupportedKey;
    }   
}

KeyCode ToX11Key(EKey InKey)
{
    switch(InKey)
    {
        case EKey::Escape:
            return 9;
        case EKey::Key1:
            return 10;
        case EKey::Key2:
            return 11;
        case EKey::Key3:
            return 12;
        case EKey::Key4:
            return 13;
        case EKey::Key5:
            return 14;
        case EKey::Key6:
            return 15;
        case EKey::Key7:
            return 16;
        case EKey::Key8:
            return 17;
        case EKey::Key9:
            return 18;
        case EKey::Key0:
            return 19;
        case EKey::Minus:
            return 20;
        case EKey::Equal:
            return 21;
        case EKey::Backspace:
            return 22;
        case EKey::Tab:
            return 23;
        case EKey::Q:
            return 24;
        case EKey::W:
            return 25;
        case EKey::E:
            return 26;
        case EKey::R:
            return 27;
        case EKey::T:
            return 28;
        case EKey::Y:
            return 29;
        case EKey::U:
            return 30;
        case EKey::I:
            return 31;
        case EKey::O:
            return 32;
        case EKey::P:
            return 33;
        case EKey::LBracket:
            return 34;
        case EKey::RBracket:
            return 35;
        case EKey::Enter:
            return 36;
        case EKey::LControl:
            return 37;
        case EKey::A:
            return 38;
        case EKey::S:
            return 39;
        case EKey::D:
            return 40;
        case EKey::F:
            return 41;
        case EKey::G:
            return 42;
        case EKey::H:
            return 43;
        case EKey::J:
            return 44;
        case EKey::K:
            return 45;
        case EKey::L:
            return 46;
        case EKey::Semicolon:
            return 47;
        case EKey::Apostrophe:
            return 48;
        case EKey::Tilde:
            return 49;
        case EKey::LShift:
            return 50;
        case EKey::BackSlash:
            return 51;
        case EKey::Z:
            return 52;
        case EKey::X:
            return 53;
        case EKey::C:
            return 54;
        case EKey::V:
            return 55;
        case EKey::B:
            return 56;
        case EKey::N:
            return 57;
        case EKey::M:
            return 58;
        case EKey::Comma:
            return 59;
        case EKey::Period:
            return 60;
        case EKey::Slash:
            return 61;
        case EKey::RShift:
            return 62;
        case EKey::NumPadMultiply:
            return 63;
        case EKey::LAlt:
            return 64;
        case EKey::Space:
            return 65;
        case EKey::CapsLock:
            return 66;
        case EKey::F1:
            return 67;
        case EKey::F2:
            return 68;
        case EKey::F3:
            return 69;
        case EKey::F4:
            return 70;
        case EKey::F5:
            return 71;
        case EKey::F6:
            return 72;
        case EKey::F7:
            return 73;
        case EKey::F8:
            return 74;
        case EKey::F9:
            return 75;
        case EKey::F10:
            return 76;
        case EKey::NumLock:
            return 77;
        case EKey::ScrollLock:
            return 78;
        case EKey::NumPad7:
            return 79;
        case EKey::NumPad8:
            return 80;
        case EKey::NumPad9:
            return 81;
        case EKey::NumPadSubtract:
            return 82;
        case EKey::NumPad4:
            return 83;
        case EKey::NumPad5:
            return 84;
        case EKey::NumPad6:
            return 85;
        case EKey::NumPadAdd:
            return 86;
        case EKey::NumPad1:
            return 87;
        case EKey::NumPad2:
            return 88;
        case EKey::NumPad3:
            return 89;
        case EKey::NumPad0:
            return 90;
        case EKey::NumPadDecimal:
            return 91;
        case EKey::F11:
            return 93;
        case EKey::F12:
            return 94;
        case EKey::NumPadEnter:
            return 104;
        case EKey::NumPadSlash:
            return 106;
        case EKey::RAlt:
            return 108;
        case EKey::Up:
            return 111;
        case EKey::Left:
            return 113;
        case EKey::Right:
            return 114;
        case EKey::RControl:
            return 115;
        case EKey::Down:
            return 116;
        case EKey::LSystem:
            return 133;
        case EKey::RSystem:
            return 134;
        case EKey::Menu:
            return 135;
        default:
            assert(false && "UnSupported Key");
            return 0;

    }
}

EMouseButton GetMouseButtonFromX11Button(unsigned int InX11MouseButton)
{
    switch(InX11MouseButton)
    {
    case 1:
        return EMouseButton::Left;
    case 2:
        return EMouseButton::Middle;
    case 3:
        return EMouseButton::Right;
    case 4:
        return EMouseButton::WheelForward;
    case 5:
        return EMouseButton::WheelBackward;
    default:
        return EMouseButton::UnSupportedButton;
    }

}

int64_t CLinuxPlatform::CreateProcessWithoutBlock(const CString &Command, const CString &WorkingDirectory)
{
    //create a new process on linux
    pid_t pid = fork();

    if (pid == 0)
    {
        //child process
        //execute the command
        CString CDCommand = U"cd " + WorkingDirectory;
        CString FinalCommand = CDCommand +U" && ./"  + Command;
        auto FinalCommand8 = FinalCommand.ToUtf8();
        execl("/bin/sh", "sh", "-c",(const char*)FinalCommand8.Data(), NULL);
    }
    else if (pid > 0)
    {
        //parent process
        return pid;
    }
    else
    {
        //fork failed
        assert(false&& "fork failed");
        return -1;
    }
}

void CLinuxPlatform::KillProcess(int64_t ProcessID)
{
    kill(ProcessID, SIGKILL);
}


TOptional<SystemEvent> CLinuxPlatform::PopEvents()
{

	static Atom XdndAware = XInternAtom(X11Things->_Display, "XdndAware", False);
	static Atom XA_ATOM = XInternAtom(X11Things->_Display, "XA_ATOM", False);
	static Atom XdndEnter = XInternAtom(X11Things->_Display, "XdndEnter", False);
	static Atom XdndPosition = XInternAtom(X11Things->_Display, "XdndPosition", False);
	static Atom XdndActionCopy = XInternAtom(X11Things->_Display, "XdndActionCopy", False);
	static Atom XdndLeave = XInternAtom(X11Things->_Display, "XdndLeave", False);
	static Atom XdndStatus = XInternAtom(X11Things->_Display, "XdndStatus", False);
	static Atom XdndDrop = XInternAtom(X11Things->_Display, "XdndDrop", False);
	static Atom XdndSelection = XInternAtom(X11Things->_Display, "XdndSelection", False);
	static Atom XDND_DATA = XInternAtom(X11Things->_Display, "XDND_DATA", False);
	static Atom XdndTypeList = XInternAtom(X11Things->_Display, "XdndTypeList", False);
	static Atom XdndFinished = XInternAtom(X11Things->_Display, "XdndFinished", False);

    //drop data type
    static Atom FileList = XInternAtom(X11Things->_Display, "text/uri-list", False);

    static Atom WindowsManager = XInternAtom(X11Things->_Display, "WM_PROTOCOLS", 0);
    static Atom DeleteWindow = XInternAtom(X11Things->_Display, "WM_DELETE_WINDOW", 0);

    static Atom XUtf8String = XInternAtom(X11Things->_Display, "UTF8_STRING", False);


    X11Things->ReadAllX11Event();

    while(X11Things->PendingEvents.Num()>0)
    {
        XEvent Event=X11Things->PendingEvents[0];
        X11Things->PendingEvents.RemoveAt(0,false);

        //filter event for input method
        if(XFilterEvent(&Event, 0))   //remove some input event , without this , cant input chinese
        {
            //skip this event
            continue;
        }



        switch(Event.type)
        {
        case FocusIn:
        {
            SSystemWindowGotFocus WindowFocusEvent;
            WindowFocusEvent.WindowID = Event.xfocus.window;
            return SystemEvent(WindowFocusEvent);
        }

        case FocusOut:
        {
            SSystemWindowLostFocus WindowFocusEvent;
            WindowFocusEvent.WindowID = Event.xfocus.window;
            WindowFocusEvent.FocusedWindowID = Event.xfocus.mode==NotifyNormal ? Event.xfocus.detail : 0;
            return SystemEvent(WindowFocusEvent);
        }

        case KeyPress:
        {

            SSystemKeyboardKeyDown KeyboardEvent;
            KeyboardEvent.WindowID = Event.xkey.window;
            KeyboardEvent.Key=GetKeyFromX11Key(Event.xkey.keycode);
            return SystemEvent(KeyboardEvent);         
        }

        case KeyRelease:
        {
            SSystemKeyboardKeyUp KeyboardEvent;
            KeyboardEvent.WindowID = Event.xkey.window;
            KeyboardEvent.Key=GetKeyFromX11Key(Event.xkey.keycode);
            return SystemEvent(KeyboardEvent);
        }
        case ButtonPress:
        {
            

            if(Event.xbutton.button==4 /* Mouse forward  */ || Event.xbutton.button== 5 /* Mouse backward */ )
            {
                //skip wheel roll  event when wheel direction button pressed
                return TOptional<SystemEvent> ();
            }

            SSystemMouseButtonDown MousePressEvent;
            MousePressEvent.WindowID = Event.xbutton.window;
            MousePressEvent.Button = GetMouseButtonFromX11Button(Event.xbutton.button);

            static uint64_t DoubleClickThreshold =  500; //ms

            //record mouse press time 
            switch(MousePressEvent.Button)
            {
            case EMouseButton::Left:
                {
                    if(Event.xbutton.time - X11Things->LastTimeLeftMouseButtonPressed < DoubleClickThreshold)
                    {
                        MousePressEvent.DoubleClick = true;
                    }
                }
                X11Things->LastTimeLeftMouseButtonPressed = Event.xbutton.time;
                break;
            case EMouseButton::Right:
                {
                    if(Event.xbutton.time - X11Things->LastTimeRightMouseButtonPressed < DoubleClickThreshold)
                    {
                        MousePressEvent.DoubleClick = true;
                    }
                }
                X11Things->LastTimeRightMouseButtonPressed = Event.xbutton.time;
                break;
            case EMouseButton::Middle:
                {
                    if(Event.xbutton.time - X11Things->LastTimeMiddleMouseButtonPressed < DoubleClickThreshold)
                    {
                        MousePressEvent.DoubleClick = true;
                    }
                }
                X11Things->LastTimeMiddleMouseButtonPressed = Event.xbutton.time;
                break;
            }

            return SystemEvent(MousePressEvent);
        }

        case ButtonRelease:
        {
            if(Event.xbutton.button==4 /* Mouse forward  */ || Event.xbutton.button== 5 /* Mouse backward */ )
            {   
                int Delta = Event.xbutton.button==4 ? 1 : -1;

                //create wheel roll event
                SSystemMouseWheelRoll MousePressEvent;
                MousePressEvent.WindowID = Event.xbutton.window; 
                MousePressEvent.WheelDistance =Delta;

                return SystemEvent(MousePressEvent);
            }

            SSystemMouseButtonUp MousePressEvent;
            MousePressEvent.WindowID = Event.xbutton.window;     
            MousePressEvent.Button = GetMouseButtonFromX11Button(Event.xbutton.button);

            return SystemEvent(MousePressEvent);
        }
        case MotionNotify:
        {

            SSystemMouseMove MouseMoveEvent;
            MouseMoveEvent.WindowID = Event.xmotion.window;
            MouseMoveEvent.MousePosition.X = Event.xmotion.x;
            MouseMoveEvent.MousePosition.Y = Event.xmotion.y;
            MouseMoveEvent.Delta.X = Event.xmotion.x_root - MouseMoveEvent.ScreenMousePosition.X;
            MouseMoveEvent.Delta.Y = Event.xmotion.y_root - MouseMoveEvent.ScreenMousePosition.Y;
            MouseMoveEvent.ScreenMousePosition.X = Event.xmotion.x_root;
            MouseMoveEvent.ScreenMousePosition.Y = Event.xmotion.y_root;
            
            return SystemEvent(MouseMoveEvent);
        }
        case ConfigureNotify:
        {
            //check if the window is resized
            if(Event.xconfigure.width>0 && Event.xconfigure.height>0)
            {
                //update window state
                auto NewWindowSize = SIntPoint(Event.xconfigure.width,Event.xconfigure.height);

                SSystemWindowResize WindowEvent;
                WindowEvent.WindowID = Event.xconfigure.window;
                WindowEvent.NewSize.X = Event.xconfigure.width;
                WindowEvent.NewSize.Y = Event.xconfigure.height;

                return SystemEvent(WindowEvent);

            }

        }
        case SelectionClear:
        {
            //lost Clipboard ownership,just clear Clipboard text
            ClipboardText.Reset();

            break;
        }
        case SelectionRequest:
        {
            //other window request Clipboard text
            auto Request = Event.xselectionrequest;

            if(Request.target!=XUtf8String
                || !ClipboardText.HasValue()
            )
            {
                //send  SelectionNotify to tell the other window that 
                //we dont support this type
                //or we dont have Clipboard text
                XEvent SelectionNotifyEvent;
                SelectionNotifyEvent.xselection.type = SelectionNotify;
                SelectionNotifyEvent.xselection.display = Request.display;
                SelectionNotifyEvent.xselection.requestor = Request.requestor;
                SelectionNotifyEvent.xselection.selection = Request.selection;
                SelectionNotifyEvent.xselection.target = Request.target;
                SelectionNotifyEvent.xselection.property = None;
                SelectionNotifyEvent.xselection.time = Request.time;
                XSendEvent(Request.display, Request.requestor, 0, 0, &SelectionNotifyEvent);
                break;
            }

            //send  SelectionNotify to tell the other window that we support this type
            XEvent SelectionNotifyEvent;
            SelectionNotifyEvent.xselection.type = SelectionNotify;
            SelectionNotifyEvent.xselection.display = Request.display;
            SelectionNotifyEvent.xselection.requestor = Request.requestor;
            SelectionNotifyEvent.xselection.selection = Request.selection;
            SelectionNotifyEvent.xselection.target = Request.target;
            SelectionNotifyEvent.xselection.property = Request.property;
            SelectionNotifyEvent.xselection.time = Request.time;

            //send Clipboard text
            auto U8Text=ClipboardText.GetValue().ToUtf8();
            XChangeProperty(Request.display, Request.requestor, Request.property, XUtf8String, 8, PropModeReplace, (unsigned char*)U8Text.Data(), U8Text.Num());
            XSendEvent(Request.display, Request.requestor, 0, 0, &SelectionNotifyEvent);
            break;
        }
        case SelectionNotify:
        {   
            //address dropped file path
            Window DropTarget = Event.xclient.window;

            //get data
            Atom actual_type;
            int actual_format;
            unsigned long nitems, bytes_after;
            unsigned char *data = NULL;
            int status = XGetWindowProperty(X11Things->_Display, DropTarget, XdndSelection, 0, 1000000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &data);
            if (status == Success)
            {
                //file path example U"/home/x/Model.png\r\n"
                CString FilePath = (char8_t*)data;
                //remove the first 'file://' prefix
                FilePath = FilePath.SubString(7);
                //remove the last '\r\n'
                FilePath = FilePath.SubString(0,FilePath.GetLength()-2);


                SSystemDropFiles MousePressEvent;
                MousePressEvent.WindowID = DropTarget;
                MousePressEvent.FilePaths.Add(FilePath);

                return SystemEvent(MousePressEvent);
            }
        }  
        case ClientMessage:
        {
            if (Event.xclient.message_type == XdndDrop) 
            {
                //get the window that the drop happened on
                Window DropTarget = Event.xclient.window;

                //request  file list from source window , 
                XConvertSelection(X11Things->_Display, XdndSelection, FileList, XdndSelection, DropTarget, CurrentTime);

            }
            else if(WindowsManager == Event.xclient.message_type)
            {
                //check if the window is trying to close
                if(Event.xclient.data.l[0] == DeleteWindow)
                {
                    SSystemWindowClose WindowEvent;
                    WindowEvent.WindowID = Event.xclient.window;

                    return SystemEvent(WindowEvent);
                }
            }

        }

        }
    }

    return TOptional<SystemEvent> ();
}

ISytemWindowInterface *CLinuxPlatform::CreateSystemWindow()
{
    auto Window = new CX11Window();
    X11Things->Windows.Add(Window->GetWindowID(),Window);
    return Window;
}

void CLinuxPlatform::DestroySystemWindow(ISytemWindowInterface *Window)
{
    X11Things->Windows.Remove(Window->GetWindowID());
    delete Window;
}

void CLinuxPlatform::StartTextInput(uint64_t WindowID, SIntPoint IMEWidgetPosition)
{
    assert(X11Things->_XIM==0  && "already started!" );

    X11Things->_XIM= XOpenIM(X11Things->_Display, NULL, NULL, NULL);
    X11Things->_XIC = XCreateIC(X11Things->_XIM, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, WindowID, NULL);
    XSetICFocus(X11Things->_XIC);

    XPoint WidgetPosition;
    WidgetPosition.x = IMEWidgetPosition.X;
    WidgetPosition.y = IMEWidgetPosition.Y;
    XVaNestedList preedit_attr;
    preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &WidgetPosition, NULL);
    XSetICValues(X11Things->_XIC, XNPreeditAttributes, preedit_attr, NULL);
    XFree(preedit_attr);

}

void CLinuxPlatform::StopTextInput()
{
    if(X11Things->_XIC==0)
    {
        return;
    }
    XUnsetICFocus(X11Things->_XIC);
    XDestroyIC(X11Things->_XIC);
    X11Things->_XIC=0;
    XCloseIM(X11Things->_XIM);
    X11Things->_XIM=0;
}

bool CLinuxPlatform::IsTextInputStarted() const
{
    return X11Things->_XIM!=0;
}

#include <X11/cursorfont.h>

unsigned int GetX11CursorType(EMouseCursorType CursorType)
{
    switch(CursorType)
    {
        case EMouseCursorType::Arrow:
            return XC_left_ptr;
        case EMouseCursorType::IBeam:
            return XC_xterm;
        case EMouseCursorType::ResizeHorizontal:
            return XC_sb_h_double_arrow;
        case EMouseCursorType::ResizeVertical:
            return XC_sb_v_double_arrow;
        case EMouseCursorType::ResizeLeftTopTORightBottom:
            return XC_bottom_right_corner;
        case EMouseCursorType::ResizeRightTopTOLeftBottom:
            return XC_bottom_left_corner;
        case EMouseCursorType::ResizeAll:
            return XC_fleur;
        case EMouseCursorType::Wait:
            return XC_watch;
        case EMouseCursorType::WaitAndArrow:
            return XC_watch;
        case EMouseCursorType::Hand:
            return XC_hand2;
        case EMouseCursorType::No:
            return XC_circle;
        default:
            return XC_left_ptr;
    }



}

void CLinuxPlatform::UseSystemCursor(EMouseCursorType CursorType)
{
    Cursor Cursor = XCreateFontCursor(X11Things->_Display, GetX11CursorType(CursorType));
    XDefineCursor(X11Things->_Display, DefaultRootWindow(X11Things->_Display), Cursor);
}

TVector<TTuple<CString, SPath>> CLinuxPlatform::GetDefaultFavoriteList()
{
    return TVector<TTuple<CString, SPath>>();
}

bool CLinuxPlatform::IsKeyDown(EKey Key)
{
    //get key state
    char Keys[32];
    XQueryKeymap(X11Things->_Display, Keys);

    KeyCode X11Key = ToX11Key(Key);
    return Keys[X11Key / 8] & (1 << (X11Key % 8));
}

void CLinuxPlatform::SetClipboardText(const CString &InText)
{
    if(InText.IsEmpty())
    {
        return;
    }

    ClipboardText = InText;

    Atom XA_Clipboard= XInternAtom(X11Things->_Display, "CLIPBOARD", False);
    
    Window ClipboardWindow=X11Things->GetClipboardWindow();
    //get the Clipboard owner,trigger other application to paste
    if(XGetSelectionOwner(X11Things->_Display, XA_Clipboard) != ClipboardWindow)
    {
        XSetSelectionOwner(X11Things->_Display, XA_Clipboard, ClipboardWindow, CurrentTime);
    }

}

bool CLinuxPlatform::HasClipboardText()
{
    return GetClipboardText().IsEmpty();
}

CString CLinuxPlatform::GetClipboardText()
{
    Atom XA_Clipboard= XInternAtom(X11Things->_Display, "CLIPBOARD", False);

    //if selection owner is null , return empty string
    if(XGetSelectionOwner(X11Things->_Display, XA_Clipboard) == None)
    {
        return CString();
    }

    //if selection owner is ClipboardWindow , return ClipboardText
    if(XGetSelectionOwner(X11Things->_Display, XA_Clipboard) == X11Things->GetClipboardWindow())
    {
        return ClipboardText.GetValue();
    }


    Atom XUtf8String = XInternAtom(X11Things->_Display, "UTF8_STRING", False);

    //property to hold text , it's arbitraty
    Atom PastTargetProperty = XInternAtom(X11Things->_Display, "Engine_Past", False);

    //require selection owner convert data on clipboard into utf8string
    XConvertSelection(
        X11Things->_Display
        ,XA_Clipboard
        ,XUtf8String
        ,PastTargetProperty
        ,X11Things->GetClipboardWindow()
        ,CurrentTime);

    int CurrentEventIndex=X11Things->PendingEvents.Num();

    CString Result;

    auto StartTime = std::chrono::steady_clock::now();
    while(true)
    {
        if(std::chrono::steady_clock::now() - StartTime > std::chrono::seconds(1))
        {
            //timeout
            break;
        }

        X11Things->ReadAllX11Event();

        if(CurrentEventIndex<X11Things->PendingEvents.Num())
        {
            XEvent& Event=X11Things->PendingEvents[CurrentEventIndex];

            if(Event.type==SelectionNotify)
            {
                auto SelectionEvent=(XSelectionEvent*)&Event.xselection;

                if(SelectionEvent->requestor!=X11Things->GetClipboardWindow())
                {
                    //this event is not for clippboard
                    continue;
                }

                if(SelectionEvent->property==None)
                {
                    //request failed!,cant convert clippboard data into utf8
                    X11Things->PendingEvents.RemoveAt(CurrentEventIndex,false);
                    break;
                }
                else if(SelectionEvent->property==PastTargetProperty)
                {
                    //got text

                    Atom actual_type;
                    int actual_format;
                    unsigned long nitems, bytes_after;
                    unsigned char *data = NULL;
                    int status = XGetWindowProperty(
                        X11Things->_Display
                        , X11Things->GetClipboardWindow()
                        , PastTargetProperty
                        , 0
                        , 1000000
                        , False
                        , AnyPropertyType
                        , &actual_type
                        , &actual_format
                        , &nitems
                        , &bytes_after
                        , &data);

                    if (status == Success)
                    {
                        Result = (char8_t*)data;
                    }

                    XFree(data);

                    X11Things->PendingEvents.RemoveAt(CurrentEventIndex,false);

                    break;


                }
            }

            CurrentEventIndex++;
        }
    }

    XDeleteProperty(X11Things->_Display, X11Things->GetClipboardWindow(), PastTargetProperty);

    return Result;


    

    // Atom XA_Clipboard= XInternAtom(X11Things->_Display, "Clipboard", False);
    // Atom XA_UTF8_STRING = XInternAtom(X11Things->_Display, "UTF8_STRING", False);

    // //get Clipboard text
    // XConvertSelection(X11Things->_Display, XA_Clipboard, XA_UTF8_STRING, XA_Clipboard, X11Things->ClipboardWindow, CurrentTime);

    // //wait for SelectionNotify event
    // XEvent Event;
    // while(true)
    // {
    //     XNextEvent(X11Things->_Display, &Event);
    //     if(Event.type == SelectionNotify)
    //     {
    //         break;
    //     }
    // }

    // //get data
    // Atom actual_type;
    // int actual_format;
    // unsigned long nitems, bytes_after;
    // unsigned char *data = NULL;
    // int status = XGetWindowProperty(X11Things->_Display, X11Things->ClipboardWindow, XA_Clipboard, 0, 1000000, False, AnyPropertyType, &actual_type, &actual_format, &nitems, &bytes_after, &data);
    // if (status == Success)
    // {
    //     //file path example U"/home/x/Model.png\r\n"
    //     CString FilePath = (char8_t*)data;
    //     //remove the first 'file://' prefix
    //     FilePath = FilePath.SubString(7);
    //     //remove the last '\r\n'
    //     FilePath = FilePath.SubString(0,FilePath.GetLength()-2);

    //     return FilePath;
    // }
    // else
    // {
    //     return CString();
    // }
}

CString CLinuxPlatform::SystemStringToUnicode(const char *InArg) const
{
    return CString((char8_t*)InArg);
}

void CLinuxPlatform::CaptureMouse(uint64_t WindowID)
{
    XGrabPointer(X11Things->_Display, WindowID, True, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime);
}

void CLinuxPlatform::ReleaseMouseCapture()
{
    XUngrabPointer(X11Things->_Display, CurrentTime);
}

#endif